/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    Copyright (C) 2004
 *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
 *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
 *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
 */

package weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI;

import weka.core.FastVector;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;
import weka.gui.LookAndFeel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.GregorianCalendar;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;

/**
 * Start the OPTICS Visualizer from command-line: <br/>
 * <code>java weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI.OPTICS_Visualizer [file.ser]</code>
 * <br/>
 * 
 * <p>
 * OPTICS_Visualizer.java <br/>
 * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht <br/>
 * Date: Sep 12, 2004 <br/>
 * Time: 8:01:13 PM <br/>
 * $ Revision 1.4 $ <br/>
 * </p>
 * 
 * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
 * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
 * @version $Revision: 4791 $
 */
public class OPTICS_Visualizer implements RevisionHandler {

	/**
	 * Holds the OPTICS clustering results
	 */
	private SERObject serObject;

	/**
	 * Main Window of the OPTICS-Visualizer
	 */
	private JFrame frame;

	/**
	 * Statistic-frame
	 */
	private JFrame statisticsFrame;

	/**
	 * Help-frame
	 */
	private JFrame helpFrame;

	/**
	 * Listener for menu- and toolBar actions
	 */
	private FrameListener frameListener;

	/**
	 * Holds the toolBar and its components
	 */
	private JToolBar toolBar;
	private JButton toolBarButton_open;
	private JButton toolBarButton_save;
	private JButton toolBarButton_parameters;
	private JButton toolBarButton_help;
	private JButton toolBarButton_about;

	/**
	 * Holds the default-menu and its components
	 */
	private JMenuBar defaultMenuBar;
	private JMenuItem open;
	private JMenuItem save;
	private JMenuItem exit;
	private JMenuItem parameters;
	private JMenuItem help;
	private JMenuItem about;

	/**
	 * Holds the tabbedPane and its components
	 */
	private JTabbedPane tabbedPane;
	private JTable resultVectorTable;
	private GraphPanel graphPanel;
	private JScrollPane graphPanelScrollPane;

	/**
	 * Holds the settingsPanel and its components
	 */
	private JPanel settingsPanel;
	private JCheckBox showCoreDistances;
	private JCheckBox showReachabilityDistances;
	private int verValue = 30;
	private JSlider verticalSlider;
	private JButton coreDistanceColorButton;
	private JButton reachDistanceColorButton;
	private JButton graphBackgroundColorButton;
	private JButton resetColorButton;

	/**
	 * FileChooser for saving- and open-actions
	 */
	private JFileChooser jFileChooser;
	private String lastPath;

	// *****************************************************************************************************************
	// constructors
	// *****************************************************************************************************************

	public OPTICS_Visualizer(SERObject serObject, String title) {
		this.serObject = serObject;

		LookAndFeel.setLookAndFeel();

		frame = new JFrame(title);

		frame.addWindowListener(new WindowAdapter() {
			/**
			 * Invoked when a window is in the process of being closed. The
			 * close operation can be overridden at this point.
			 */
			public void windowClosing(WindowEvent e) {
				frame.dispose();
			}
		});

		frame.getContentPane().setLayout(new BorderLayout());
		frame.setSize(new Dimension(800, 600));
		Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
		Rectangle windowRectangle = frame.getBounds();
		frame.setLocation((screenDimension.width - windowRectangle.width) / 2,
				(screenDimension.height - windowRectangle.height) / 2);

		frameListener = new FrameListener();
		jFileChooser = new JFileChooser();
		jFileChooser.setFileFilter(new SERFileFilter("ser",
				"Java Serialized Object File (*.ser)"));

		createGUI();
		frame.setVisible(true);
		frame.toFront();

	}

	// *****************************************************************************************************************
	// methods
	// *****************************************************************************************************************

	/**
	 * Constructs the main-layout for the OPTICS-Visualizer
	 */
	private void createGUI() {
		setMenuBar(constructDefaultMenuBar());

		frame.getContentPane().add(createToolBar(), BorderLayout.NORTH);
		frame.getContentPane().add(createTabbedPane(), BorderLayout.CENTER);
		frame.getContentPane().add(createSettingsPanel(), BorderLayout.SOUTH);
		disableSettingsPanel();
	}

	/**
	 * Creates the settings-panel
	 * 
	 * @return Settings-panel
	 */
	private JComponent createSettingsPanel() {
		settingsPanel = new JPanel(new GridBagLayout());

		SettingsPanelListener panelListener = new SettingsPanelListener();

		JPanel setPanelLeft = new JPanel(new GridBagLayout());
		setPanelLeft.setBorder(BorderFactory
				.createTitledBorder(" General Settings "));

		JPanel checkBoxesPanel = new JPanel(new GridLayout(1, 2));
		showCoreDistances = new JCheckBox("Show Core-Distances");
		showCoreDistances.setSelected(true);
		showReachabilityDistances = new JCheckBox("Show Reachability-Distances");
		showReachabilityDistances.setSelected(true);
		showCoreDistances.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED) {
					graphPanel.setShowCoreDistances(true);
					graphPanel.adjustSize(serObject);
					graphPanel.repaint();
				} else if (e.getStateChange() == ItemEvent.DESELECTED) {
					graphPanel.setShowCoreDistances(false);
					graphPanel.adjustSize(serObject);
					graphPanel.repaint();
				}
			}
		});
		showReachabilityDistances.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED) {
					graphPanel.setShowReachabilityDistances(true);
					graphPanel.adjustSize(serObject);
					graphPanel.repaint();
				} else if (e.getStateChange() == ItemEvent.DESELECTED) {
					graphPanel.setShowReachabilityDistances(false);
					graphPanel.adjustSize(serObject);
					graphPanel.repaint();
				}
			}
		});

		checkBoxesPanel.add(showCoreDistances);
		checkBoxesPanel.add(showReachabilityDistances);

		JPanel verticalAdPanel = new JPanel(new BorderLayout());
		final JLabel verValueLabel = new JLabel("Vertical Adjustment: "
				+ verValue);
		verticalAdPanel.add(verValueLabel, BorderLayout.NORTH);
		verticalSlider = new JSlider(JSlider.HORIZONTAL, 0, frame.getHeight(),
				verValue);
		verticalSlider.setMajorTickSpacing(100);
		verticalSlider.setMinorTickSpacing(10);
		verticalSlider.setPaintTicks(true);
		verticalSlider.setPaintLabels(true);
		verticalSlider.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				if (!verticalSlider.getValueIsAdjusting()) {
					verValue = verticalSlider.getValue();
					verValueLabel.setText("Vertical Adjustment: " + verValue);
					graphPanel.setVerticalAdjustment(verValue);
					graphPanel.repaint();
				}
			}
		});
		verticalAdPanel.add(verticalSlider, BorderLayout.CENTER);

		setPanelLeft.add(checkBoxesPanel, new GridBagConstraints(0, 0, 1, 1, 1,
				1, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
				new Insets(5, 5, 5, 5), 0, 0));
		setPanelLeft.add(verticalAdPanel, new GridBagConstraints(0, 1, 1, 1, 1,
				1, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
				new Insets(5, 5, 5, 5), 0, 0));

		settingsPanel.add(setPanelLeft, new GridBagConstraints(0, 0, 1, 1, 3,
				1, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
				new Insets(5, 5, 5, 0), 0, 0));

		JPanel setPanelRight = new JPanel(new GridBagLayout());
		setPanelRight.setBorder(BorderFactory.createTitledBorder(" Colors "));

		JPanel colorsPanel = new JPanel(new GridLayout(4, 2, 10, 10));

		colorsPanel.add(new JLabel("Core-Distance: "));
		coreDistanceColorButton = new JButton();
		coreDistanceColorButton.setBackground(new Color(100, 100, 100));
		coreDistanceColorButton.addActionListener(panelListener);
		colorsPanel.add(coreDistanceColorButton);

		colorsPanel.add(new JLabel("Reachability-Distance: "));
		reachDistanceColorButton = new JButton();
		reachDistanceColorButton.setBackground(Color.orange);
		reachDistanceColorButton.addActionListener(panelListener);
		colorsPanel.add(reachDistanceColorButton);

		colorsPanel.add(new JLabel("Graph Background: "));
		graphBackgroundColorButton = new JButton();
		graphBackgroundColorButton.setBackground(new Color(255, 255, 179));
		graphBackgroundColorButton.addActionListener(panelListener);
		colorsPanel.add(graphBackgroundColorButton);

		colorsPanel.add(new JLabel());
		resetColorButton = new JButton("Reset");
		resetColorButton.addActionListener(panelListener);
		colorsPanel.add(resetColorButton);

		setPanelRight.add(colorsPanel, new GridBagConstraints(0, 0, 1, 1, 1, 1,
				GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(
						5, 5, 5, 5), 0, 0));

		settingsPanel.add(setPanelRight, new GridBagConstraints(1, 0, 1, 1, 1,
				1, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
				new Insets(5, 5, 5, 5), 0, 0));

		return settingsPanel;
	}

	/**
	 * Disables all components from the settingsPanel
	 */
	private void disableSettingsPanel() {

		verticalSlider.setEnabled(false);
		coreDistanceColorButton.setEnabled(false);
		reachDistanceColorButton.setEnabled(false);
		graphBackgroundColorButton.setEnabled(false);
		resetColorButton.setEnabled(false);
		settingsPanel.setVisible(false);
	}

	/**
	 * Enables all components from the settingsPanel
	 */
	private void enableSettingsPanel() {
		verticalSlider.setEnabled(true);
		coreDistanceColorButton.setEnabled(true);
		reachDistanceColorButton.setEnabled(true);
		graphBackgroundColorButton.setEnabled(true);
		resetColorButton.setEnabled(true);
		settingsPanel.setVisible(true);
	}

	/**
	 * Creates the TabbedPane
	 * 
	 * @return TabbedPane
	 */
	private JComponent createTabbedPane() {
		tabbedPane = new JTabbedPane();
		tabbedPane
				.addTab("Table",
						new ImageIcon(
								Toolkit.getDefaultToolkit()
										.getImage(
												ClassLoader
														.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Table16.gif"))),
						clusteringResultsTable(),
						"Show table of DataObjects, Core- and Reachability-Distances");
		if (serObject != null)
			tabbedPane
					.addTab("Graph - Epsilon: " + serObject.getEpsilon()
							+ ", MinPoints: " + serObject.getMinPoints(),
							new ImageIcon(
									Toolkit.getDefaultToolkit()
											.getImage(
													ClassLoader
															.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Graph16.gif"))),
							graphPanel(),
							"Show Plot of Core- and Reachability-Distances");
		else
			tabbedPane
					.addTab("Graph - Epsilon: --, MinPoints: --",
							new ImageIcon(
									Toolkit.getDefaultToolkit()
											.getImage(
													ClassLoader
															.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Graph16.gif"))),
							graphPanel(),
							"Show Plot of Core- and Reachability-Distances");

		tabbedPane.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				int c = tabbedPane.getSelectedIndex();
				if (c == 0)
					disableSettingsPanel();
				else
					enableSettingsPanel();
			}
		});

		return tabbedPane;
	}

	/**
	 * Creates the ToolBar
	 * 
	 * @return ToolBar
	 */
	private JComponent createToolBar() {
		toolBar = new JToolBar();
		toolBar.setName("OPTICS Visualizer ToolBar");
		toolBar.setFloatable(false);
		toolBarButton_open = new JButton(
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Open16.gif"))));
		toolBarButton_open.setToolTipText("Open OPTICS-Session");
		toolBarButton_open.addActionListener(frameListener);
		toolBar.add(toolBarButton_open);

		toolBarButton_save = new JButton(
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Save16.gif"))));
		toolBarButton_save.setToolTipText("Save OPTICS-Session");
		toolBarButton_save.addActionListener(frameListener);
		toolBar.add(toolBarButton_save);
		toolBar.addSeparator(new Dimension(10, 25));

		toolBarButton_parameters = new JButton(
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Parameters16.gif"))));
		toolBarButton_parameters.setToolTipText("Show epsilon, MinPoints...");
		toolBarButton_parameters.addActionListener(frameListener);
		toolBar.add(toolBarButton_parameters);

		toolBar.addSeparator(new Dimension(10, 25));

		toolBarButton_help = new JButton(
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Help16.gif"))));
		toolBarButton_help.setToolTipText("Help topics");
		toolBarButton_help.addActionListener(frameListener);
		toolBar.add(toolBarButton_help);

		toolBarButton_about = new JButton(
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Information16.gif"))));
		toolBarButton_about.setToolTipText("About");
		toolBarButton_about.addActionListener(frameListener);
		toolBar.add(toolBarButton_about);

		return toolBar;
	}

	/**
	 * Creates the OPTICS clustering results table
	 * 
	 * @return Table
	 */
	private JComponent clusteringResultsTable() {
		resultVectorTable = new JTable();
		String[] resultVectorTableColumnNames = { "Key", "DataObject",
				"Core-Distance", "Reachability-Distance" };

		DefaultTableColumnModel resultVectorTableColumnModel = new DefaultTableColumnModel();
		for (int i = 0; i < resultVectorTableColumnNames.length; i++) {
			TableColumn tc = new TableColumn(i);
			tc.setHeaderValue(resultVectorTableColumnNames[i]);
			resultVectorTableColumnModel.addColumn(tc);
		}

		ResultVectorTableModel resultVectorTableModel;
		if (serObject != null)
			resultVectorTableModel = new ResultVectorTableModel(
					serObject.getResultVector());
		else
			resultVectorTableModel = new ResultVectorTableModel(null);
		resultVectorTable = new JTable(resultVectorTableModel,
				resultVectorTableColumnModel);
		resultVectorTable.getColumnModel().getColumn(0).setPreferredWidth(70);
		resultVectorTable.getColumnModel().getColumn(1).setPreferredWidth(400);
		resultVectorTable.getColumnModel().getColumn(2).setPreferredWidth(150);
		resultVectorTable.getColumnModel().getColumn(3).setPreferredWidth(150);
		resultVectorTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

		JScrollPane resultVectorTableScrollPane = new JScrollPane(
				resultVectorTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

		return resultVectorTableScrollPane;
	}

	/**
	 * Creates the OPTICS Plot
	 * 
	 * @return JComponent with the PLOT
	 */
	private JComponent graphPanel() {

		if (serObject == null) {
			graphPanel = new GraphPanel(new FastVector(), verValue, true, true);
		} else {
			graphPanel = new GraphPanel(serObject.getResultVector(), verValue,
					true, true);
			graphPanel.setPreferredSize(new Dimension((10 * serObject
					.getDatabaseSize()) + serObject.getDatabaseSize(),
					graphPanel.getHeight()));
		}
		graphPanel.setBackground(new Color(255, 255, 179));
		graphPanel.setOpaque(true);

		graphPanelScrollPane = new JScrollPane(graphPanel,
				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

		return (graphPanelScrollPane);
	}

	/**
	 * Constructs the default MenuBar
	 * 
	 * @return MenuBar
	 */
	private JMenuBar constructDefaultMenuBar() {
		defaultMenuBar = new JMenuBar();

		JMenu fileMenu = new JMenu("File");
		fileMenu.setMnemonic('F');
		open = new JMenuItem(
				"Open...",
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Open16.gif"))));
		open.setMnemonic('O');
		open.setAccelerator(KeyStroke.getKeyStroke('O', Event.CTRL_MASK));
		open.addActionListener(frameListener);
		fileMenu.add(open);

		save = new JMenuItem(
				"Save...",
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Save16.gif"))));
		save.setMnemonic('S');
		save.setAccelerator(KeyStroke.getKeyStroke('S', Event.CTRL_MASK));
		save.addActionListener(frameListener);
		fileMenu.add(save);

		fileMenu.addSeparator();

		exit = new JMenuItem("Exit", 'X');
		exit.addActionListener(frameListener);
		fileMenu.add(exit);

		defaultMenuBar.add(fileMenu);

		JMenu toolsMenu = new JMenu("View");
		toolsMenu.setMnemonic('V');
		parameters = new JMenuItem(
				"Parameters...",
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Parameters16.gif"))));
		parameters.setMnemonic('P');
		parameters.setAccelerator(KeyStroke.getKeyStroke('P', Event.CTRL_MASK));
		parameters.addActionListener(frameListener);
		toolsMenu.add(parameters);

		defaultMenuBar.add(toolsMenu);

		JMenu miscMenu = new JMenu("Help");
		miscMenu.setMnemonic('H');
		help = new JMenuItem(
				"Help Topics",
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Help16.gif"))));
		help.setMnemonic('H');
		help.setAccelerator(KeyStroke.getKeyStroke('H', Event.CTRL_MASK));
		help.addActionListener(frameListener);
		miscMenu.add(help);

		about = new JMenuItem(
				"About...",
				new ImageIcon(
						Toolkit.getDefaultToolkit()
								.getImage(
										ClassLoader
												.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Information16.gif"))));
		about.setMnemonic('A');
		about.setAccelerator(KeyStroke.getKeyStroke('A', Event.CTRL_MASK));
		about.addActionListener(frameListener);
		miscMenu.add(about);
		defaultMenuBar.add(miscMenu);

		return defaultMenuBar;
	}

	/**
	 * Sets a MenuBar for the this frame
	 * 
	 * @param menuBar
	 *            New MenuBar
	 */
	private void setMenuBar(JMenuBar menuBar) {
		frame.setJMenuBar(menuBar);
	}

	/**
	 * Shows a little frame with statistic information about the OPTICS-results
	 */
	private void loadStatisticsFrame() {
		statisticsFrame = new JFrame("Parameters");
		statisticsFrame.getContentPane().setLayout(new BorderLayout());

		JPanel statPanel_Labels = new JPanel(new GridBagLayout());
		JPanel statPanel_Labels_Left = new JPanel(new GridLayout(9, 1));
		JPanel statPanel_Labels_Right = new JPanel(new GridLayout(9, 1));

		statPanel_Labels_Left.add(new JLabel(
				"Number of clustered DataObjects: "));
		statPanel_Labels_Right.add(new JLabel(Integer.toString(serObject
				.getDatabaseSize())));
		statPanel_Labels_Left.add(new JLabel("Number of attributes: "));
		statPanel_Labels_Right.add(new JLabel(Integer.toString(serObject
				.getNumberOfAttributes())));
		statPanel_Labels_Left.add(new JLabel("Epsilon: "));
		statPanel_Labels_Right.add(new JLabel(Double.toString(serObject
				.getEpsilon())));
		statPanel_Labels_Left.add(new JLabel("MinPoints: "));
		statPanel_Labels_Right.add(new JLabel(Integer.toString(serObject
				.getMinPoints())));
		statPanel_Labels_Left.add(new JLabel("Write results to file: "));
		statPanel_Labels_Right.add(new JLabel(
				serObject.isOpticsOutputs() ? "yes" : "no"));
		statPanel_Labels_Left.add(new JLabel("Index: "));
		statPanel_Labels_Right.add(new JLabel(serObject.getDatabase_Type()));
		statPanel_Labels_Left.add(new JLabel("Distance-Type: "));
		statPanel_Labels_Right.add(new JLabel(serObject
				.getDatabase_distanceType()));
		statPanel_Labels_Left.add(new JLabel("Number of generated clusters: "));
		statPanel_Labels_Right.add(new JLabel(Integer.toString(serObject
				.getNumberOfGeneratedClusters())));
		statPanel_Labels_Left.add(new JLabel("Elapsed-time: "));
		statPanel_Labels_Right.add(new JLabel(serObject.getElapsedTime()));
		statPanel_Labels.setBorder(BorderFactory
				.createTitledBorder(" OPTICS parameters "));

		statPanel_Labels.add(statPanel_Labels_Left, new GridBagConstraints(0,
				0, 1, 1, 1, 1, GridBagConstraints.CENTER,
				GridBagConstraints.BOTH, new Insets(0, 5, 2, 0), 0, 0));

		statPanel_Labels.add(statPanel_Labels_Right, new GridBagConstraints(1,
				0, 1, 1, 3, 1, GridBagConstraints.CENTER,
				GridBagConstraints.BOTH, new Insets(0, 5, 2, 5), 0, 0));

		statisticsFrame.getContentPane().add(statPanel_Labels,
				BorderLayout.CENTER);

		statisticsFrame.addWindowListener(new WindowAdapter() {
			/**
			 * Invoked when a window is in the process of being closed. The
			 * close operation can be overridden at this point.
			 */
			public void windowClosing(WindowEvent e) {
				statisticsFrame.dispose();
			}
		});

		JPanel okButtonPanel = new JPanel(new GridBagLayout());

		JButton okButton = new JButton("OK");
		okButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (e.getActionCommand().equals("OK")) {
					statisticsFrame.dispose();
				}
			}
		});
		okButtonPanel.add(okButton, new GridBagConstraints(0, 0, 1, 1, 1, 1,
				GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(
						5, 0, 5, 0), 0, 0));

		statisticsFrame.getContentPane().add(okButtonPanel, BorderLayout.SOUTH);
		statisticsFrame.setSize(new Dimension(500, 300));
		Rectangle frameDimension = frame.getBounds();
		Point p = frame.getLocation();
		Rectangle statisticsFrameDimension = statisticsFrame.getBounds();
		statisticsFrame.setLocation(
				((frameDimension.width - statisticsFrameDimension.width) / 2)
						+ (int) p.getX(),
				((frameDimension.height - statisticsFrameDimension.height) / 2)
						+ (int) p.getY());
		statisticsFrame.setVisible(true);
		statisticsFrame.toFront();
	}

	/**
	 * Shows a little frame with information about handling the OPTICS
	 * Visualizer
	 */
	private void loadHelpFrame() {
		helpFrame = new JFrame("Help Topics");
		helpFrame.getContentPane().setLayout(new BorderLayout());

		JPanel helpPanel = new JPanel(new GridBagLayout());
		JTextArea helpTextArea = new JTextArea();
		helpTextArea.setEditable(false);
		helpTextArea
				.append("OPTICS Visualizer Help\n"
						+ "===========================================================\n\n"
						+ "Open\n"
						+ " - Open OPTICS-Session\n"
						+ "   [Ctrl-O], File | Open\n\n"
						+ "Save\n"
						+ " - Save OPTICS-Session\n"
						+ "   [Ctrl-S], File | Save\n\n"
						+ "Exit\n"
						+ " - Exit OPTICS Visualizer\n"
						+ "   [Alt-F4], File | Exit\n\n"
						+ "Parameters\n"
						+ " - Show epsilon, MinPoints...\n"
						+ "   [Ctrl-P], View | Parameters\n\n"
						+ "Help Topics\n"
						+ " - Show this frame\n"
						+ "   [Ctrl-H], Help | Help Topics\n\n"
						+ "About\n"
						+ " - Copyright-Information\n"
						+ "   [Ctrl-A], Help | About\n\n\n"
						+ "Table-Pane:\n"
						+ "-----------------------------------------------------------\n"
						+ "The table represents the calculated clustering-order.\n"
						+ "To save the table please select File | Save from the\n"
						+ "menubar. Restart OPTICS with the -F option to obtain\n"
						+ "an ASCII-formatted file of the clustering-order.\n\n"
						+ "Graph-Pane:\n"
						+ "-----------------------------------------------------------\n"
						+ "The graph draws the plot of core- and reachability-\n"
						+ "distances. By (de-)activating core- and reachability-\n"
						+ "distances in the 'General Settings'-Panel you can\n"
						+ "influence the visualization in detail. Simply use the\n"
						+ "'Vertical Adjustment'-Slider to emphasize the plot of\n"
						+ "distances. The 'Colors'-Panel lets you define different\n"
						+ "colors of the graph background, core- and reachability-\n"
						+ "distances. Click the 'Reset'-Button to restore the\n"
						+ "defaults.\n");
		final JScrollPane helpTextAreaScrollPane = new JScrollPane(
				helpTextArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		helpTextAreaScrollPane.setBorder(BorderFactory.createEtchedBorder());
		helpPanel.add(helpTextAreaScrollPane, new GridBagConstraints(0, 0, 1,
				1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
				new Insets(5, 5, 7, 5), 0, 0));

		helpFrame.getContentPane().add(helpPanel, BorderLayout.CENTER);

		helpFrame.addWindowListener(new WindowAdapter() {
			/**
			 * Invoked when a window is in the process of being closed. The
			 * close operation can be overridden at this point.
			 */
			public void windowClosing(WindowEvent e) {
				helpFrame.dispose();
			}

			/**
			 * Invoked when a window has been opened.
			 */
			public void windowOpened(WindowEvent e) {
				helpTextAreaScrollPane.getVerticalScrollBar().setValue(0);
			}
		});

		JPanel closeButtonPanel = new JPanel(new GridBagLayout());

		JButton closeButton = new JButton("Close");
		closeButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (e.getActionCommand().equals("Close")) {
					helpFrame.dispose();
				}
			}
		});
		closeButtonPanel.add(closeButton, new GridBagConstraints(0, 0, 1, 1, 1,
				1, GridBagConstraints.CENTER, GridBagConstraints.NONE,
				new Insets(0, 0, 5, 0), 0, 0));

		helpFrame.getContentPane().add(closeButtonPanel, BorderLayout.SOUTH);
		helpFrame.setSize(new Dimension(480, 400));
		Rectangle frameDimension = frame.getBounds();
		Point p = frame.getLocation();
		Rectangle helpFrameDimension = helpFrame.getBounds();
		helpFrame.setLocation(
				((frameDimension.width - helpFrameDimension.width) / 2)
						+ (int) p.getX(),
				((frameDimension.height - helpFrameDimension.height) / 2)
						+ (int) p.getY());
		helpFrame.setVisible(true);
		helpFrame.toFront();
	}

	/**
	 * Returns the revision string.
	 * 
	 * @return the revision
	 */
	public String getRevision() {
		return RevisionUtils.extract("$Revision: 4791 $");
	}

	/**
	 * Displays the GUI. If an optics file is provided as first parameter, this
	 * will be loaded and displayed automatically.
	 * 
	 * @param args
	 *            the commandline parameters
	 * @throws Exception
	 *             if something goes wrong
	 */
	public static void main(String[] args) throws Exception {
		SERObject serObject = null;
		if (args.length == 1) {
			System.out.println("Attempting to load: " + args[0]);
			ObjectInputStream is = null;
			try {
				FileInputStream fs = new FileInputStream(args[0]);
				is = new ObjectInputStream(fs);
				serObject = (SERObject) is.readObject();
			} catch (Exception e) {
				serObject = null;
				JOptionPane.showMessageDialog(null,
						"Error loading file:\n" + e, "Error",
						JOptionPane.ERROR_MESSAGE);
			} finally {
				try {
					is.close();
				} catch (Exception e) {
					// ignored
				}
			}
		}

		// open GUI
		new OPTICS_Visualizer(serObject, "OPTICS Visualizer - Main Window");
	}

	// *****************************************************************************************************************
	// inner classes
	// *****************************************************************************************************************

	private class FrameListener implements ActionListener, RevisionHandler {

		/**
		 * Invoked when an action occurs.
		 */
		public void actionPerformed(ActionEvent e) {
			if (e.getSource() == parameters
					|| e.getSource() == toolBarButton_parameters) {
				loadStatisticsFrame();
			}

			if (e.getSource() == about || e.getSource() == toolBarButton_about) {
				JOptionPane.showMessageDialog(frame,
						"OPTICS Visualizer\n$ Rev 1.4 $\n\nCopyright (C) 2004 "
								+ "Rainer Holzmann, Zhanna Melnikova-Albrecht",
						"About", JOptionPane.INFORMATION_MESSAGE);
			}

			if (e.getSource() == help || e.getSource() == toolBarButton_help) {
				loadHelpFrame();
			}

			if (e.getSource() == exit) {
				frame.dispose();
			}

			if (e.getSource() == open || e.getSource() == toolBarButton_open) {
				jFileChooser.setDialogTitle("Open OPTICS-Session");
				if (lastPath == null) {
					lastPath = System.getProperty("user.dir");
				}
				jFileChooser.setCurrentDirectory(new File(lastPath));
				int ret = jFileChooser.showOpenDialog(frame);
				SERObject serObject_1 = null;
				if (ret == JFileChooser.APPROVE_OPTION) {
					File f = jFileChooser.getSelectedFile();
					try {
						FileInputStream fs = new FileInputStream(
								f.getAbsolutePath());
						ObjectInputStream is = new ObjectInputStream(fs);
						serObject_1 = (SERObject) is.readObject();
						is.close();
					} catch (FileNotFoundException e1) {
						JOptionPane.showMessageDialog(frame, "File not found.",
								"Error", JOptionPane.ERROR_MESSAGE);
					} catch (ClassNotFoundException e1) {
						JOptionPane.showMessageDialog(frame,
								"OPTICS-Session could not be read.", "Error",
								JOptionPane.ERROR_MESSAGE);
					} catch (IOException e1) {
						JOptionPane
								.showMessageDialog(
										frame,
										"This file does not contain a valid OPTICS-Session.",
										"Error", JOptionPane.ERROR_MESSAGE);
					}
					if (serObject_1 != null) {
						int ret_1 = JOptionPane.showConfirmDialog(frame,
								"Open OPTICS-Session in a new window?", "Open",
								1);
						switch (ret_1) {
						case 0:
							new OPTICS_Visualizer(serObject_1,
									"OPTICS Visualizer - " + f.getName());
							break;
						case 1:
							serObject = serObject_1;
							resultVectorTable
									.setModel(new ResultVectorTableModel(
											serObject.getResultVector()));
							tabbedPane.setTitleAt(1, "Graph - Epsilon: "
									+ serObject.getEpsilon() + ", MinPoints: "
									+ serObject.getMinPoints());
							graphPanel.setResultVector(serObject
									.getResultVector());
							graphPanel.adjustSize(serObject);
							graphPanel.repaint();
							break;
						default:
							break;
						}
					}
				}
			}

			if (e.getSource() == save || e.getSource() == toolBarButton_save) {
				jFileChooser.setDialogTitle("Save OPTICS-Session");

				GregorianCalendar gregorianCalendar = new GregorianCalendar();
				String timeStamp = gregorianCalendar.get(Calendar.DAY_OF_MONTH)
						+ "-" + (gregorianCalendar.get(Calendar.MONTH) + 1)
						+ "-" + gregorianCalendar.get(Calendar.YEAR) + "--"
						+ gregorianCalendar.get(Calendar.HOUR_OF_DAY) + "-"
						+ gregorianCalendar.get(Calendar.MINUTE) + "-"
						+ gregorianCalendar.get(Calendar.SECOND);
				String filename = "OPTICS_" + timeStamp + ".ser";

				File file = new File(filename);
				jFileChooser.setSelectedFile(file);
				if (lastPath == null) {
					lastPath = System.getProperty("user.dir");
				}
				jFileChooser.setCurrentDirectory(new File(lastPath));

				int ret = jFileChooser.showSaveDialog(frame);
				if (ret == JFileChooser.APPROVE_OPTION) {
					file = jFileChooser.getSelectedFile();
					try {
						FileOutputStream fs = new FileOutputStream(
								file.getAbsolutePath());
						ObjectOutputStream os = new ObjectOutputStream(fs);
						os.writeObject(serObject);
						os.flush();
						os.close();
					} catch (IOException e1) {
						JOptionPane.showMessageDialog(frame,
								"OPTICS-Session could not be saved.", "Error",
								JOptionPane.ERROR_MESSAGE);
					}
				}
			}
		}

		/**
		 * Returns the revision string.
		 * 
		 * @return the revision
		 */
		public String getRevision() {
			return RevisionUtils.extract("$Revision: 4791 $");
		}
	}

	private class SettingsPanelListener implements ActionListener,
			RevisionHandler {

		/**
		 * Invoked when an action occurs.
		 */
		public void actionPerformed(ActionEvent e) {
			if (e.getSource() == coreDistanceColorButton) {
				Color c = getSelectedColor("Select 'Core-Distance' color");
				if (c != null) {
					coreDistanceColorButton.setBackground(c);
					graphPanel.setCoreDistanceColor(c);
				}
			}
			if (e.getSource() == reachDistanceColorButton) {
				Color c = getSelectedColor("Select 'Reachability-Distance' color");
				if (c != null) {
					reachDistanceColorButton.setBackground(c);
					graphPanel.setReachabilityDistanceColor(c);
				}
			}
			if (e.getSource() == graphBackgroundColorButton) {
				Color c = getSelectedColor("Select 'Graph Background' color");
				if (c != null) {
					graphBackgroundColorButton.setBackground(c);
					graphPanel.setBackground(c);
				}
			}
			if (e.getSource() == resetColorButton) {
				coreDistanceColorButton.setBackground(new Color(100, 100, 100));
				graphPanel.setCoreDistanceColor(new Color(100, 100, 100));
				reachDistanceColorButton.setBackground(Color.orange);
				graphPanel.setReachabilityDistanceColor(Color.orange);
				graphBackgroundColorButton.setBackground(new Color(255, 255,
						179));
				graphPanel.setBackground(new Color(255, 255, 179));
				graphPanel.repaint();
			}
		}

		private Color getSelectedColor(String title) {
			Color c = JColorChooser.showDialog(frame, title, Color.black);
			return c;
		}

		/**
		 * Returns the revision string.
		 * 
		 * @return the revision
		 */
		public String getRevision() {
			return RevisionUtils.extract("$Revision: 4791 $");
		}
	}
}
